package com.example.android.raycast;

import java.io.IOException;
import java.io.InputStream;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.view.SurfaceHolder;

class RaycastThread extends Thread {

	
	/** Handle to the application context, used to e.g. fetch Drawables. */
	private Context mContext;
	
    // Describes the image format our bitmaps should be converted to.
    private static BitmapFactory.Options sBitmapOptions 
        = new BitmapFactory.Options();
	
	// State-tracking constants
	public static final int STATE_LOSE = 1;
	public static final int STATE_PAUSE = 2;
	public static final int STATE_RUNNING = 4;
	public static final int STATE_WIN = 5;

	// Current height/width of the surface/canvas.
	private int mCanvasHeight = 200;
	private int mCanvasWidth = 320;
	// Message handler used by thread to interact with TextView
	private Handler mHandler;

	// Used to figure out elapsed time between frames
	private long mLastTime;

	// The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN
	private int mMode;

	// Indicate whether the surface has been created & is ready to draw/
	private boolean mRun = false;

	// Handle to the surface manager object we interact with
	private SurfaceHolder mSurfaceHolder;

	private boolean mShowFiring;
	private String mStatusText = " FPS: ";
	
	private Rect mArrowsBitmapRect;
	
	private Bitmap[] mBitmaps;

	private int ANGLE60 = mCanvasWidth;
	private int ANGLE30 = (ANGLE60 / 2);
	private int ANGLE90 = (ANGLE30 * 3);
	private int ANGLE180 = (ANGLE90 * 2);
	private int ANGLE270 = (ANGLE90 * 3);
	private int ANGLE360 = (ANGLE60 * 6);	
	private final int ANGLE0 = 0;

	private final int TEXTURE_WIDTH = 64;
	private final int TEXTURE_HEIGHT = 64;

	private int mFps = 0;
	private long mLastFpsTime = 0;
	private int mLastFpsCount = 0;

	private float[] mZBuffer;
	private int[] mWallBottoms;

	int mDistanceToTheProjectionPlane = 277;
	int mProjectionPlaneYCenter = mCanvasHeight / 2;
	// trigonometric tables
	float mSinTable[];
	float mISinTable[];
	float mCosTable[];
	float mICosTable[];
	float mTanTable[];
	float mITanTable[];
	float mFishTable[];
	float mXStepTable[];
	float mYStepTable[];

	public static int TILE_SIZE = 64;
	private final int WALL_HEIGHT = 64;
	
	private Player mPlayer = null;
	private int mLevelNumber = 1;
	private Level mLevel;
	
	public static int mArrowsBitmapIndex = 17;
	private Paint mFirePaint;
	private Paint mTextPaint;
	private Paint mSimplePaint;
	


	// *******************************************************************//
	// * Convert arc to radian
	// *******************************************************************//
	float arcToRad(float arcAngle) {
		return ((float) (arcAngle * Math.PI) / (float) ANGLE180);
	}

	// *******************************************************************//
	// * Create trigonometric values to make the program run faster.
	// *******************************************************************//
	public void createTrigTables() {
		int i;
		float radian;
		mSinTable = new float[ANGLE360 + 1];
		mISinTable = new float[ANGLE360 + 1];
		mCosTable = new float[ANGLE360 + 1];
		mICosTable = new float[ANGLE360 + 1];
		mTanTable = new float[ANGLE360 + 1];
		mITanTable = new float[ANGLE360 + 1];
		mFishTable = new float[ANGLE60 + 1];
		mXStepTable = new float[ANGLE360 + 1];
		mYStepTable = new float[ANGLE360 + 1];

		for (i = 0; i <= ANGLE360; i++) {
			// get the radian value (the last addition is to avoid division
			// by 0, try removing
			// that and you'll see a hole in the wall when a ray is at 0,
			// 90, 180, or 270 degree)
			radian = arcToRad(i) + (float) (0.0001);
			mSinTable[i] = (float) Math.sin(radian);
			mISinTable[i] = (1.0F / (mSinTable[i]));
			mCosTable[i] = (float) Math.cos(radian);
			mICosTable[i] = (1.0F / (mCosTable[i]));
			mTanTable[i] = (float) Math.tan(radian);
			mITanTable[i] = (1.0F / mTanTable[i]);

			//Work out the step tables
			if (i >= ANGLE90 && i < ANGLE270) {
				// facing left
				mXStepTable[i] = (float) (TILE_SIZE / mTanTable[i]);
				if (mXStepTable[i] > 0) {
					mXStepTable[i] = -mXStepTable[i];
				}
			} else {
				// facing right
				mXStepTable[i] = (float) (TILE_SIZE / mTanTable[i]);
				if (mXStepTable[i] < 0) {
					mXStepTable[i] = -mXStepTable[i];
				}
			}
			
			if (i >= ANGLE0 && i < ANGLE180) {
				// FACING DOWN
				mYStepTable[i] = (float) (TILE_SIZE * mTanTable[i]);
				if (mYStepTable[i] < 0) {
					mYStepTable[i] = -mYStepTable[i];
				}
			} else {
				// FACING UP
				mYStepTable[i] = (float) (TILE_SIZE * mTanTable[i]);
				if (mYStepTable[i] > 0) {
					mYStepTable[i] = -mYStepTable[i];
				}
			}
		}

		for (i = -ANGLE30; i <= ANGLE30; i++) {
			radian = arcToRad(i);
			// we don't have negative angle, so make it start at 0
			// this will give range 0 to 320
			mFishTable[i + ANGLE30] = (float) (1.0F / Math.cos(radian));
		}
	}

	/***
	 * The main RaycastActivity thread
	 * 
	 * @param surfaceHolder
	 * @param context
	 * @param handler
	 */
	public RaycastThread(SurfaceHolder surfaceHolder, Context context,int startLevelNumber,
			Handler handler) {
		
		Log.w(this.getClass().getName(), "RaycastThread.ctor");
		
		// get handles to some important objects
		mSurfaceHolder = surfaceHolder;
		mHandler = handler;
		mContext = context;
		
        // Sets our preferred image format to 16-bit, 565 format.
        sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;

		// Set up the paints etc
        mSimplePaint = new Paint();
        mSimplePaint.setAntiAlias(false);
        mSimplePaint.setDither(false);
        mSimplePaint.setFilterBitmap(false);

		mFirePaint = new Paint();
		mFirePaint.setAntiAlias(false);
		mFirePaint.setDither(false);
		mFirePaint.setFilterBitmap(false);
		mFirePaint.setColor(Color.RED);

		mTextPaint = new Paint();
		mTextPaint.setAntiAlias(false);
		mTextPaint.setDither(false);
		mTextPaint.setFilterBitmap(false);
		mTextPaint.setColor(Color.BLACK);
		
		// Load the textures
		// size to last door plus 1, so we can index by mBitmap[wallValue] ie 1 based
		// rather than zero based
		mBitmaps = new Bitmap[Level.DOOR_END+1];
		mBitmaps[0] = loadBitmap(mContext, R.drawable.barrel);
		mBitmaps[1] = loadBitmap(mContext, R.drawable.bluestone);
		mBitmaps[2] = loadBitmap(mContext, R.drawable.colorstone);
		mBitmaps[3] = loadBitmap(mContext, R.drawable.eagle);
		mBitmaps[4] = loadBitmap(mContext, R.drawable.greenlight);
		mBitmaps[5] = loadBitmap(mContext, R.drawable.greystone);
		mBitmaps[6] = loadBitmap(mContext, R.drawable.mossy);
		mBitmaps[7] = loadBitmap(mContext, R.drawable.pillar);
		mBitmaps[8] = loadBitmap(mContext, R.drawable.purplestone);
		mBitmaps[9] = loadBitmap(mContext, R.drawable.redbrick);
		mBitmaps[10] = loadBitmap(mContext, R.drawable.wood);
        mBitmaps[11] = loadBitmap(mContext, R.drawable.barrel);
        mBitmaps[12] = loadBitmap(mContext, R.drawable.pillar);
        mBitmaps[13] = loadBitmap(mContext, R.drawable.purplestone);
        mBitmaps[14] = loadBitmap(mContext, R.drawable.redbrick);
        mBitmaps[15] = loadBitmap(mContext, R.drawable.wood);
        mBitmaps[16] = loadBitmap(mContext, R.drawable.wood); //was gun2
        mBitmaps[mArrowsBitmapIndex] = loadBitmap(mContext, R.drawable.btn_zoom_page_normal);
        //Doors are like walls when they are shut!
        //TODO have some different doors
        mBitmaps[18] = loadBitmap(mContext, R.drawable.steel_door);
        mBitmaps[19] = loadBitmap(mContext, R.drawable.steel_door);
        
        
        //left top right bottom
        int arrowsHeight = mBitmaps[mArrowsBitmapIndex].getHeight();
        int arrowsWidth = mBitmaps[mArrowsBitmapIndex].getWidth();
        mArrowsBitmapRect = new Rect(0,mCanvasHeight - arrowsHeight ,arrowsWidth,mCanvasHeight);
        
        mLevel = new Level(startLevelNumber, context);

		createTrigTables();
		
		if(mPlayer == null) {
			mPlayer = new Player(Weapon.WEAPON_SHOTGUN, mLevel.getCurrentLevelPlayerStartX(), mLevel.getCurrentLevelPlayerStartY(),mCanvasWidth,mCanvasHeight,mContext);
		} else {
			mPlayer.updateAngles(mCanvasWidth,mCanvasHeight);
		}
	}
	

    /**
     * Loads a bitmap from a resource and converts it to a bitmap.  This is
     * a much-simplified version of the loadBitmap() that appears in
     * SimpleGLRenderer.
     * @param context  The application context.
     * @param resourceId  The id of the resource to load.
     * @return  A bitmap containing the image contents of the resource, or null
     *     if there was an error.
     */
    protected Bitmap loadBitmap(Context context, int resourceId) {
        Bitmap bitmap = null;
        if (context != null) {
          
            InputStream is = context.getResources().openRawResource(resourceId);
            try {
                bitmap = BitmapFactory.decodeStream(is, null, sBitmapOptions);
            } finally {
                try {
                    is.close();
                } catch (IOException e) {
                    // Ignore.
                }
            }
        }

        return bitmap;
    }

	/**
	 * Starts the game, setting parameters for the current difficulty.
	 */
	public void restart() {
		synchronized (mSurfaceHolder) {
			
			Log.w(this.getClass().getName(), "RaycastThread.restart");
			
			mLastTime = System.currentTimeMillis() + 100;
			
			//Start at first level
			mLevel = new Level(1, mContext);
			mLevelNumber = 1;
			
			mPlayer.reset();

			setState(STATE_RUNNING);
		}
	}

	/**
	 * Pauses the physics update & animation.
	 */
	public void pause() {
		synchronized (mSurfaceHolder) {
			if (mMode == STATE_RUNNING) {
				setState(STATE_PAUSE);
			}
		}
	}

	/**
	 * Restores game state from the indicated Bundle. Typically called when
	 * the Activity is being restored after having been previously
	 * destroyed.
	 * 
	 * @param savedState
	 *            Bundle containing the game state
	 */
	public synchronized void restoreState(Bundle savedState) {
		synchronized (mSurfaceHolder) {
			setState(STATE_PAUSE);
			
			mPlayer.restoreState(savedState);
			//TODO restoreState of all monsters
			//TODO restoreState of all doors
		}
	}
	
	/**
	 * Dump game state to the provided Bundle. Typically called when the
	 * Activity is being suspended.
	 * 
	 * @return Bundle with this view's state
	 */
	public Bundle saveState(Bundle map) {
		synchronized (mSurfaceHolder) {
			if (map != null) {
				map = mPlayer.saveState(map);
				//TODO saveState of all monsters
				//TODO saveState of all doors
			}
		}
		return map;
	}

	/***
	 * Thread run override - lock the canvas, update physics and draw the
	 * screen Main game loop
	 */
	@Override
	public void run() {
		Log.w(this.getClass().getName(), "RaycastThread.run before loop");
		while (mRun) {
			Canvas c = null;
			try {
				c = mSurfaceHolder.lockCanvas(null);
				synchronized (mSurfaceHolder) {
					if (mMode == STATE_RUNNING) {
						updatePhysics();
					
					drawBackground(c);
					rayCast(c);
					drawMonstersAndSprites(c);
					drawDoors(c);
					drawFiring(c);
					drawOnScreenControls(c);
					}
				}
			} finally {
				// do this in a finally so that if an exception is thrown
				// during the above, we don't leave the Surface in an
				// inconsistent state
				if (c != null) {
					mSurfaceHolder.unlockCanvasAndPost(c);
				}
			}
		}
	}

	private void drawDoors(Canvas canvas) {
		for (int x = 0; x < mLevel.getCurrentLevelNumberOfDoors(); x++) {
			//OK to work with a temp copy as we do not need to modify the door
			//Door testDoor = mLevel.getCurrentLevelDoors()[x];
			if(mLevel.getCurrentLevelDoors()[x].getSeen()) {

				//Work out how to draw the door based on its height
				int projectedDoorHeight = (int) (mLevel.getCurrentLevelDoors()[x].getHeight() * (float) mDistanceToTheProjectionPlane / mLevel.getCurrentLevelDoors()[x].getDistance());
				int bottomOfDoor = mProjectionPlaneYCenter + (projectedDoorHeight / 2);
				if (bottomOfDoor >= mCanvasHeight) {
					bottomOfDoor = mCanvasHeight;
				}
				int topOfDoor = mCanvasHeight - bottomOfDoor;
				
				//Work out how to draw the door based on its width and how much of it is open
				int left = mLevel.getCurrentLevelDoors()[x].getSeenAtFirstColumn();
				int currentColsTaken = mLevel.getCurrentLevelDoors()[x].getSeenAtLastColumn() - mLevel.getCurrentLevelDoors()[x].getSeenAtFirstColumn(); 
				float amountOpen = mLevel.getCurrentLevelDoors()[x].getAmountOpen();
				float width = mLevel.getCurrentLevelDoors()[x].getWidth();
				float percentOpen = amountOpen/width;
				int newColsToUse = (int) (currentColsTaken * percentOpen);
				int diff = currentColsTaken - newColsToUse;
				int newLeftPos = left + diff;
				
				//left top right bottom
				RectF dstRect = new RectF(newLeftPos, topOfDoor,mLevel.getCurrentLevelDoors()[x].getSeenAtLastColumn(),bottomOfDoor);
				//RectF dstRect = new RectF(mMonsters[x].getSeenAtFirstColumn(), topOfMonster,mMonsters[x].getSeenAtLastColumn(),bottomOfMonster);					
				canvas.drawBitmap(mLevel.getCurrentLevelDoors()[x].getCurrentBitmap(), mLevel.getCurrentLevelDoors()[x].getSourceRect(), dstRect, mSimplePaint);
			}//seen true			
		}//for each door			
	}

	/**
	 * Used to signal the thread whether it should be running or not.
	 * Passing true allows the thread to run; passing false will shut it
	 * down if it's already running. Calling start() after this was most
	 * recently called with false will result in an immediate shutdown.
	 * 
	 * @param b
	 *            true to run, false to shut down
	 */
	public void setRunning(boolean b) {
			mRun = b;
			Log.w(this.getClass().getName(), "RaycastThread.setRunning called");
	}

	/**
	 * Sets the game mode. That is, whether we are running, paused, in the
	 * failure state, in the victory state, etc.
	 * 
	 * @see #setState(int, CharSequence)
	 * @param mode
	 *            one of the STATE_* constants
	 */
	public void setState(int mode) {
		synchronized (mSurfaceHolder) {
			setState(mode, null);
		}
	}

	/**
	 * Sets the game mode. That is, whether we are running, paused, in the
	 * failure state, in the victory state, etc.
	 * 
	 * @param mode
	 *            one of the STATE_* constants
	 * @param message
	 *            string to add to screen or null
	 */
	public void setState(int mode, CharSequence message) {
		/*
		 * This method optionally can cause a text message to be displayed
		 * to the user when the mode changes. Since the View that actually
		 * renders that text is part of the main View hierarchy and not
		 * owned by this thread, we can't touch the state of that View.
		 * Instead we use a Message + Handler to relay commands to the main
		 * thread, which updates the user-text View.
		 */
		synchronized (mSurfaceHolder) {
			mMode = mode;
		}
	}

	/***
	 * Callback invoked when the surface dimensions change.
	 */
	public void setSurfaceSize(int width, int height) {
		
		if(width != mCanvasWidth || height != mCanvasHeight) {			
			
			Log.w(this.getClass().getName(), "RaycastThread.setSurfaceSize called, has changed");
			// synchronized to make sure these all change atomically
			synchronized (mSurfaceHolder) {
				
				//This must be an even number, of the ANGLE constants will
				//not be correct!
				mCanvasWidth = width;
				if(mCanvasWidth %2 != 0) {
					mCanvasWidth--;
				}
				
				mCanvasHeight = height;
				
		        //left top right bottom
		        int arrowsHeight = mBitmaps[mArrowsBitmapIndex].getHeight();
		        int arrowsWidth = mBitmaps[mArrowsBitmapIndex].getWidth();
		        mArrowsBitmapRect = new Rect(0,mCanvasHeight - arrowsHeight ,arrowsWidth,mCanvasHeight);

				//30 degrees is half field of view
				double radians = (30 * Math.PI) / 180;
				mDistanceToTheProjectionPlane = (int) ((mCanvasWidth / 2) / Math
						.tan(radians));
				mProjectionPlaneYCenter = mCanvasHeight / 2;

				ANGLE60 = mCanvasWidth;
				ANGLE30 = (ANGLE60 / 2);
				ANGLE90 = (ANGLE30 * 3);
				ANGLE180 = (ANGLE90 * 2);
				ANGLE270 = (ANGLE90 * 3);
				ANGLE360 = (ANGLE60 * 6);

				if(mPlayer == null) {
					mPlayer = new Player(Weapon.WEAPON_SHOTGUN, mLevel.getCurrentLevelPlayerStartX(), mLevel.getCurrentLevelPlayerStartY(),mCanvasWidth,mCanvasHeight,mContext);
				} else {
					mPlayer.updateAngles(mCanvasWidth, mCanvasHeight);
				}
				
				createTrigTables();
			}
		}
	}

	/**
	 * Resumes from a pause.
	 */
	public void unpause() {
		// Move the real time clock up to now
		synchronized (mSurfaceHolder) {
			mLastTime = System.currentTimeMillis() + 100;
		}
		setState(STATE_RUNNING);
	}

	/**
	 * Handles a key-down event.
	 * 
	 * @param keyCode
	 *            the key that was pressed
	 * @param msg
	 *            the original event object
	 * @return true
	 */
	boolean doKeyDown(int keyCode, KeyEvent msg) {
		//synchronized (mSurfaceHolder) {
			boolean okStart = false;
			if (keyCode == KeyEvent.KEYCODE_DPAD_UP)
				okStart = true;
			if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
				okStart = true;
			if (keyCode == KeyEvent.KEYCODE_S)
				okStart = true;

			// boolean center = (keyCode == KeyEvent.KEYCODE_DPAD_UP);

			if (okStart && mMode == STATE_WIN) {
				// ready-to-start -> start
				restart();
				return true;
			} else if (mMode == STATE_PAUSE && okStart) {
				// paused -> running
				unpause();
				return true;
			} else if (mMode == STATE_RUNNING) {
				if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
						|| keyCode == KeyEvent.KEYCODE_SPACE) {
					// FIRE
					mPlayer.setFiring(true);
					return true;
				} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
						|| keyCode == KeyEvent.KEYCODE_Q) {
					// ROTATE LEFT
					mPlayer.setRotating(Player.ROTATE_LEFT);
					return true;
				} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
						|| keyCode == KeyEvent.KEYCODE_W) {
					// ROTATE RIGHT
					mPlayer.setRotating(Player.ROTATE_RIGHT);
					return true;
				} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
					// WALK FORWARD
					mPlayer.setWalking(Player.WALK_FORWARD);
					return true;
				} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
					// WALK BACKWARD
					mPlayer.setWalking(Player.WALK_BACKWARD);
					return true;
				}
			}

			return false;
		//}
	}

	/**
	 * Handles a key-up event.
	 * 
	 * @param keyCode
	 *            the key that was pressed
	 * @param msg
	 *            the original event object
	 * @return true if the key was handled and consumed, or else false
	 */
	boolean doKeyUp(int keyCode, KeyEvent msg) {
		boolean handled = false;

		//synchronized (mSurfaceHolder) {
			if (mMode == STATE_RUNNING) {
				if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
						|| keyCode == KeyEvent.KEYCODE_SPACE) {
					// STOP FIRING
					mPlayer.setFiring(false);
					handled = true;
				} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
						|| keyCode == KeyEvent.KEYCODE_Q
						|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
						|| keyCode == KeyEvent.KEYCODE_W) {
					// STOP ROTATING
					mPlayer.setRotating(Player.ROTATE_NONE);
					handled = true;
				} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP
						|| keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
					// STOP WALKING
					mPlayer.setWalking(Player.WALK_NONE);
					handled = true;
				}
			}
		//}

		return handled;
	}
	
	private void drawBackground(Canvas canvas) {
		// Clear the background with a single colour
		//canvas.drawColor(Color.LTGRAY);

		// draw the sky with a blueish gradient
		int c = 25;
		int r;
		// left top right bottom
		for (r = 0; r < mCanvasHeight / 2; r += 5) {
			RectF skyRect = new RectF(0, r, mCanvasWidth, r + 10);				
			mSimplePaint.setColor(Color.rgb(c, 125, 225));
			canvas.drawRect(skyRect, mSimplePaint);
			c += 7;
		}
		// draw the ground with a redish gradient
		c = 22;
		for (; r < mCanvasHeight; r += 5) {
			RectF groundRect = new RectF(0, r, mCanvasWidth, r + 15);				
			mSimplePaint.setColor(Color.rgb(c, 20, 20));
			canvas.drawRect(groundRect, mSimplePaint);
			c += 5;
		}
	}
	
	private void drawMonstersAndSprites(Canvas canvas) {			
		//TODO sort the visible monsters by distance, with furthest once first
		for (int x = 0; x < mLevel.getCurrentLevelNumberOfMonstersAndSprites(); x++) {
			//Use a temp copy as we dont need to modify it
			//Monster testMonster = mLevel.getCurrentLevelMonsters()[x];
			if(mLevel.getCurrentLevelMonstersAndSprites()[x].getSeen()) {
				
				//Go through from seen at first to seen at last, if a wall is closer then dont draw it
				boolean draw = false;
				float distance =  mLevel.getCurrentLevelMonstersAndSprites()[x].getDistance();
				for(int i = mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtFirstColumn(); i <= mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtLastColumn();i++) {
					if(distance < mZBuffer[i]) {
						//draw this stripe
						draw = true;
					}
				}

				if(draw) {
					//Work out how to draw the monster based on its height
					int projectedMonsterHeight = (int) (mLevel.getCurrentLevelMonstersAndSprites()[x].getHeight() * (float) mDistanceToTheProjectionPlane / mLevel.getCurrentLevelMonstersAndSprites()[x].getDistance());
					int centerOfMonsterOnScreen = (mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtLastColumn() + mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtFirstColumn()) / 2;
					
					//Assume is a monster not a sprite
					int bottomOfMonster = mProjectionPlaneYCenter + (projectedMonsterHeight / 2);
					if (bottomOfMonster >= mCanvasHeight) {
						bottomOfMonster = mCanvasHeight;
					}
					int topOfMonster = mCanvasHeight - bottomOfMonster;
					
					//If a sprite then draw it from the bottom of the wall rather than
					//the center of the wall height
					if(mLevel.getCurrentLevelMonstersAndSprites()[x].isSprite()) {
						bottomOfMonster = mWallBottoms[centerOfMonsterOnScreen];
						topOfMonster = bottomOfMonster - projectedMonsterHeight;
						
					}

					//Work out how to draw the monster based on its width
					int scaleFactor = TILE_SIZE/mLevel.getCurrentLevelMonstersAndSprites()[x].getWidth();
					int currentColsTaken = mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtLastColumn() - mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtFirstColumn(); //12-2=10
					int newColsToUse = currentColsTaken /  scaleFactor;
					//int centerOfMonsterOnScreen = (mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtLastColumn() + mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtFirstColumn()) / 2;
					int newLeftPos =  centerOfMonsterOnScreen - (newColsToUse /2);
					int newRightPos = centerOfMonsterOnScreen + (newColsToUse /2);
					
					//left top right bottom
					RectF dstRect = new RectF(newLeftPos, topOfMonster,newRightPos,bottomOfMonster);
					//RectF dstRect = new RectF(mMonsters[x].getSeenAtFirstColumn(), topOfMonster,mMonsters[x].getSeenAtLastColumn(),bottomOfMonster);					
					canvas.drawBitmap(mLevel.getCurrentLevelMonstersAndSprites()[x].getCurrentBitmap(), mLevel.getCurrentLevelMonstersAndSprites()[x].getSourceRect(), dstRect, mSimplePaint);
				}//draw
			}//seen true			
		}//for each monster
	}

	/**
	 * Draws raycasted walls and monsters
	 */
	private void rayCast(Canvas canvas) {

		// Horizontal or vertical coordinate of intersection
		// Theoretically, these will be multiple of TILE_SIZE, but some trick did here might cause
		// the values off by 1
		int verticalGrid; 			
		int horizontalGrid;
		
		// how far to the next bound (this is a multiple of tile size)
		int distToNextVerticalGrid; 
		int distToNextHorizontalGrid;
		
		// x and y intersections
		float xIntersection; 
		float yIntersection;
		float distToNextXIntersection;
		float distToNextYIntersection;

		// the current cell that the ray is in
		int xGridIndex = 0;
		int yGridIndex = 0;

		// store of where grid location hit by the x and y rays
		int xHitXGridIndex = 0;
		int xHitYGridIndex = 0;
		int yHitXGridIndex = 0;
		int yHitYGridIndex = 0;

		// store of the texture location hit by the x and y rays
		int xHitTextureOffset = 0;
		int yHitTextureOffset = 0;
		
		// the texture being used 
		int theTextureOffset = 0;
		Bitmap theTexture;

		// the distance of the x and y ray intersections from the viewpoint
		float distToVerticalGridBeingHit; 
		float distToHorizontalGridBeingHit; 
		
		//Reset the seen status for monsters and doors before we do the raycast loop
		mLevel.resetSeenForRaycast();

        int castArc = mPlayer.getAngle();
		// field of view is 60 degree with the point of view (player's
		// direction in the middle)
		castArc -= ANGLE30;
		// wrap around if necessary
		if (castArc < 0) {
			castArc = ANGLE360 + castArc;
		}
		
		//get the players x and y once, as we need them multiple times in the main loop
		int playerX = mPlayer.getX();
		int playerY = mPlayer.getY();

		
		//Store the distance used for each slice, when we draw monsters they
		//will only be drawn if the distance is less than this distance
		mZBuffer = new float[mCanvasWidth];
		//Store the wall bottoms for each slice, when we draw sprites
		//the bottom of them will be at the bottom of the wall as they are on the ground
		mWallBottoms = new int[mCanvasWidth];
		
		/***
		 * The main raycast render loop, renders each column of the screen in turn from left to right
		 * There are 7 steps:
		 * 	1) work out the X increments, which are different if the ray is facing up or down
		 * 	2) Cast a ray along the x axis until we find a horizontal wall and record the distance 
		 *     and position of the wall
		 * 	3) work out the Y increments, which are different if the ray is facing left or right
		 * 	4) Cast a ray along the y axis until we find a vertical wall and record the distance 
		 * 	   and position of the wall
		 * 	5) determine if the wall hit by the xray or yray is closer and store the hit data for the closer wall
		 * 	6) draw the wall slice for this column	
		 * 	7) Increment the angle of the ray for the next iteration of the loop 
		 */
		int castColumnIncrement = 1;
		for (int castColumn = 0; castColumn < mCanvasWidth; castColumn += castColumnIncrement) {
			//CastArc is increment at the end of the loop after the slice has been drawn
			//by castColumnIncrement, so we need to keep all the following logic in this loop
			

			
			//Step 1: work out the X increments, which are different if the ray is facing up or down
			if (castArc > ANGLE0 && castArc < ANGLE180) {
				//ray facing down
				
				// truncate then add to get the coordinate of the FIRST
				// grid (horizontal wall) that is in front of the player 
				//(this is in pixel unit) ROUND DOWN
				horizontalGrid = (playerY / TILE_SIZE) * TILE_SIZE + TILE_SIZE;

				// compute distance to the next horizontal wall
				distToNextHorizontalGrid = TILE_SIZE;

				float xtemp = mITanTable[castArc] * (horizontalGrid - playerY);
				// we can get the vertical distance to that wall by
				// (horizontalGrid-GLplayerY)
				// we can get the horizontal distance to that wall by
				// 1/tan(arc)*verticalDistance
				// find the x intersection to that wall
				xIntersection = xtemp + playerX;
			} else {
				//ray facing up
				horizontalGrid = (playerY / TILE_SIZE) * TILE_SIZE;
				distToNextHorizontalGrid = -TILE_SIZE;

				float xtemp = mITanTable[castArc] * (horizontalGrid - playerY);
				xIntersection = xtemp + playerX;

				horizontalGrid--;
			}

			// Step 2: Cast a ray along the x axis until we find a horizontal wall
			// and record the distance and position of the wall
			if (castArc == ANGLE0 || castArc == ANGLE180) {
				distToHorizontalGridBeingHit = Float.MAX_VALUE;
			} else {
				// else, move the ray until it hits a horizontal wall
				distToNextXIntersection = mXStepTable[castArc];
				while (true) {
					xGridIndex = (int) (xIntersection / TILE_SIZE);
					// in the picture, yGridIndex will be 1
					yGridIndex = (horizontalGrid / TILE_SIZE);
					
					//Check not out of bounds
					if ((xGridIndex >= Level.MAP_WIDTH) || (yGridIndex >= Level.MAP_HEIGHT) 
							|| xGridIndex < 0 || yGridIndex < 0) {
						
						distToHorizontalGridBeingHit = Float.MAX_VALUE;						
						break;
					} else if (mLevel.isWall(yGridIndex,xGridIndex)) {
						
						distToHorizontalGridBeingHit = (xIntersection - playerX) * mICosTable[castArc];
	
						// Save the location of the hit so we can pick the
						// correct texture to use for the wall after the 
						// other ray has been cast
						xHitXGridIndex = xGridIndex;
						xHitYGridIndex = yGridIndex;
						xHitTextureOffset = (int) (xIntersection % TEXTURE_WIDTH);
						xHitTextureOffset = TEXTURE_WIDTH - xHitTextureOffset - 1;
						break;
					} else {
						// If it was a monster then store it, then extend to the next ray		
						
						if(mLevel.isMonsterOrSprite(yGridIndex,xGridIndex)) {								
							//Only store the item hit data if a sprite or a monster thats not dead
							if(mLevel.isSprite(yGridIndex, xGridIndex) || mLevel.isMonsterAlive(yGridIndex,xGridIndex)) {
								int monsterId = mLevel.getCurrentLevelMap()[yGridIndex][xGridIndex] - Level.MONSTER_START;
								mLevel.getCurrentLevelMonstersAndSprites()[monsterId].hasBeenSeen();																		
								distToHorizontalGridBeingHit = (xIntersection - playerX) * mICosTable[castArc];																	
								mLevel.getCurrentLevelMonstersAndSprites()[monsterId].updateXDistance(distToHorizontalGridBeingHit);
								mLevel.getCurrentLevelMonstersAndSprites()[monsterId].setSeenData(castColumn,distToHorizontalGridBeingHit,xIntersection);
							} 
							else if(mLevel.isMonsterDead(yGridIndex, xGridIndex)){
								//remove the monster from the map
								mLevel.setToFloor(yGridIndex,xGridIndex);
							}
						} else if(mLevel.isDoor(yGridIndex,xGridIndex)) {
							//found a door!
							//if door state is closed, then treat it like a wall, ie end it here
							//if door is open, continue as it ray not blocked
							//if door opening or closing, record how much of it was seen, so
							//we can draw the sprite correctly depending on how far open it was, then
							//extend to the next ray
							
							
							if(mLevel.isDoorClosed(yGridIndex, xGridIndex)) {
								distToHorizontalGridBeingHit = (xIntersection - playerX) * mICosTable[castArc];
	
								// Save the location of the hit so we can pick the
								// correct texture to use for the wall after the 
								// other ray has been cast
								xHitXGridIndex = xGridIndex;
								xHitYGridIndex = yGridIndex;
								xHitTextureOffset = (int) (xIntersection % TEXTURE_WIDTH);
								xHitTextureOffset = TEXTURE_WIDTH - xHitTextureOffset - 1;
								break;
							} else if (mLevel.isDoorOpen(yGridIndex, xGridIndex)) {
								//Nothing to do - we extend to the next block when we 
								//drop out this if block
							} else if(mLevel.isDoorOpeningOrClosing(yGridIndex, xGridIndex)) {
									//Don't think anything different to do here for opening or closing
									int doorId = mLevel.getCurrentLevelMap()[yGridIndex][xGridIndex] - Level.DOOR_START;	
									mLevel.getCurrentLevelDoors()[doorId].hasBeenSeen();	
									distToHorizontalGridBeingHit = (xIntersection - playerX) * mICosTable[castArc];
									mLevel.getCurrentLevelDoors()[doorId].setSeenData(castColumn,distToHorizontalGridBeingHit);
							}
							
							
						}
						
						//the ray is not blocked, extend to the next block
						xIntersection += distToNextXIntersection;
						horizontalGrid += distToNextHorizontalGrid;
					}
				}
			}

			//Step 3: work out the Y increments, which are different if the ray is facing left or right
			if (castArc < ANGLE90 || castArc > ANGLE270) {
				//ray facing right
				verticalGrid = TILE_SIZE + (playerX / TILE_SIZE) * TILE_SIZE;
				distToNextVerticalGrid = TILE_SIZE;

				float ytemp = mTanTable[castArc] * (verticalGrid - playerX);
				yIntersection = ytemp + playerY;
			} else { 
				// ray facing left
				verticalGrid = (playerX / TILE_SIZE) * TILE_SIZE;
				distToNextVerticalGrid = -TILE_SIZE;

				float ytemp = mTanTable[castArc] * (verticalGrid - playerX);
				yIntersection = ytemp + playerY;

				verticalGrid--;
			}

			// Step 4: Cast a ray along the y axis until we find a vertical wall
			// and record the distance and position of the wall
			if (castArc == ANGLE90 || castArc == ANGLE270) {
				distToVerticalGridBeingHit = Float.MAX_VALUE;
			} else {
				distToNextYIntersection = mYStepTable[castArc];// /2;
				while (true) {
					// compute current map position to inspect
					xGridIndex = (verticalGrid / TILE_SIZE);
					yGridIndex = (int) (yIntersection / TILE_SIZE);

					//Check map bounds
					if ((xGridIndex >= Level.MAP_WIDTH) || (yGridIndex >= Level.MAP_HEIGHT) 
							|| xGridIndex < 0 || yGridIndex < 0) {
						distToVerticalGridBeingHit = Float.MAX_VALUE;
						break;
					} else if (mLevel.isWall(yGridIndex, xGridIndex))  {
						
						distToVerticalGridBeingHit = (yIntersection - playerY) * mISinTable[castArc];							
						
						// Save the location of the hit so we can pick the
						// correct texture to use for the wall after the 
						// other ray has been cast
						yHitXGridIndex = xGridIndex;
						yHitYGridIndex = yGridIndex;
						yHitTextureOffset = (int) (yIntersection % TEXTURE_WIDTH);							
						break;
					} else {							
						// If it was a monster then store it, then extend to the next ray							
						if(mLevel.isMonsterOrSprite(yGridIndex, xGridIndex)) {								
							//Only store the item hit data if a sprite or a monster thats not dead
							if(mLevel.isSprite(yGridIndex, xGridIndex) || mLevel.isMonsterAlive(yGridIndex,xGridIndex))  {
								int monsterId = mLevel.getCurrentLevelMap()[yGridIndex][xGridIndex] - Level.MONSTER_START;
								mLevel.getCurrentLevelMonstersAndSprites()[monsterId].hasBeenSeen();										
								distToVerticalGridBeingHit = (yIntersection - playerY) * mISinTable[castArc];								
								mLevel.getCurrentLevelMonstersAndSprites()[monsterId].updateYDistance(distToVerticalGridBeingHit);
								mLevel.getCurrentLevelMonstersAndSprites()[monsterId].setSeenData(castColumn,distToVerticalGridBeingHit,yIntersection);
							} 
							else if(mLevel.isMonsterDead(yGridIndex, xGridIndex)){
								//remove the monster from the map
								mLevel.setToFloor(yGridIndex, xGridIndex);
							}
						} else if(mLevel.isDoor(yGridIndex,xGridIndex)) {
							//found a door!
							//if door state is closed, then treat it like a wall, ie end it here
							//if door is open, continue as it ray not blocked
							//if door opening or closing, record how much of it was seen, so
							//we can draw the sprite correctly depending on how far open it was, then
							//extend to the next ray							
							if(mLevel.isDoorClosed(yGridIndex, xGridIndex)) {
								distToVerticalGridBeingHit = (yIntersection - playerY) * mISinTable[castArc];
	
								// Save the location of the hit so we can pick the
								// correct texture to use for the wall after the 
								// other ray has been cast
								xHitXGridIndex = xGridIndex;
								xHitYGridIndex = yGridIndex;
								xHitTextureOffset = (int) (xIntersection % TEXTURE_WIDTH);
								
								break;
							} else if (mLevel.isDoorOpen(yGridIndex, xGridIndex)) {
								//Nothing to do - we extend to the next block when we 
								//drop out this if block
							} else if(mLevel.isDoorOpeningOrClosing(yGridIndex, xGridIndex)) {
									int doorId = mLevel.getCurrentLevelMap()[yGridIndex][xGridIndex] - Level.DOOR_START;
									//Don't think anything different to do here for opening or closing
									mLevel.getCurrentLevelDoors()[doorId].hasBeenSeen();	
									distToVerticalGridBeingHit = (yIntersection - playerY) * mISinTable[castArc];
									mLevel.getCurrentLevelDoors()[doorId].setSeenData(castColumn,distToVerticalGridBeingHit);
							}
							
						}
						
						//the ray is not blocked, extend to the next block
						yIntersection += distToNextYIntersection;
						verticalGrid += distToNextVerticalGrid;
					}
				}
			}

			// Step 5: determine if the wall hit by the xray or yray is closer
			// and store the hit data for the closer wall
			float dist;
			if (distToHorizontalGridBeingHit < distToVerticalGridBeingHit) {
				dist = distToHorizontalGridBeingHit;
				theTextureOffset = xHitTextureOffset;
				theTexture = mBitmaps[mLevel.getCurrentLevelMap()[xHitYGridIndex][xHitXGridIndex]];	
			} else {
				// else, we use xray instead (meaning the vertical wall is closer than
				// the horizontal wall)
				dist = distToVerticalGridBeingHit;
				theTextureOffset = yHitTextureOffset;
				theTexture = mBitmaps[mLevel.getCurrentLevelMap()[yHitYGridIndex][yHitXGridIndex]];
			}
			
			// Step 6: draw the wall slice for this column					
			// correct distance (compensate for the fish eye effect)
			dist /= mFishTable[castColumn];
			
			//Store the distance for this column, so we can determine later if
			//to draw a monster that was spotted in this column - we dont want
			//to if it was on the furthest away ray
			mZBuffer[castColumn] = dist;
			
			// calculate the wall height
			//cant do this in a lookup as we use a float dist for accuracy
			int projectedWallHeight = (int) (WALL_HEIGHT * (float) mDistanceToTheProjectionPlane / dist);

			int bottomOfWall = mProjectionPlaneYCenter + (projectedWallHeight / 2);
			if (bottomOfWall >= mCanvasHeight) {
				bottomOfWall = mCanvasHeight;
			}
			mWallBottoms[castColumn] = bottomOfWall;
			
			int topOfWall = mCanvasHeight - bottomOfWall;

			// *****START OF TEXTURED BITMAP METHOD
			// The source rectangle is the single column of the texture bitmap we want to draw
			// its size is width 1 and height 64
			Rect srcRect = new Rect(theTextureOffset,0,theTextureOffset+castColumnIncrement,TEXTURE_HEIGHT);
			// The destination of the rectangle is the single column of the screen we want the texture slice
			// drawn onto. As the height of the wall is likely to be different to the native texture height
			// it will need to be scaled (width is still 1)
			RectF dstRect = new RectF(castColumn, topOfWall,castColumn+castColumnIncrement,bottomOfWall);				
			//TODO draw this slice into a temp bitmap, that we then draw at the end				
			canvas.drawBitmap(theTexture, srcRect, dstRect, mSimplePaint);
			
			

			// *******START OF ORIGINAL NONTEXTURED COLOUR FILL METHOD
			// left top right bottom
			// RectF wallRect = new
			// RectF(castColumn,topOfWall,castColumn+castColumnIncrement,topOfWall+projectedWallHeight);
			// canvas.drawRect(wallRect, rayPaint);

			// Step 7: Increment the angle of the ray for the next iteration of the loop 
			castArc += castColumnIncrement;
			if (castArc >= ANGLE360) {
				castArc -= ANGLE360;
			}
		}//for cast column loop
	}
	
	private void drawFiring(Canvas canvas) {
		// Draw shotgun fire from bottom-right corner diagonally to centre
		if (mPlayer.getFiring() && mShowFiring && mPlayer.getWeapon().getAmmo() > 0) {
			int ySpace = mCanvasHeight / 12;
			int xSpace = mCanvasWidth / 12;
			int[] yPoses = new int[] { ySpace * 6, ySpace * 7, ySpace * 8,
					ySpace * 9 };
			int[] xPoses = new int[] { xSpace * 6, xSpace * 7, xSpace * 8,
					xSpace * 9 };
			for (int i = 0; i < 4; i++) {
				float radius = 0;
				if(i == 0) {
					radius = (float)mPlayer.getWeapon().getRange();
				} else {
					radius = (mCanvasHeight - yPoses[i]) / 10;
				}
				
				// Start x right of centre to account for gun image
				canvas.drawCircle(xPoses[i], yPoses[i], radius, mFirePaint);
			}
		}
	}
		
	public Rect getArrowsBitmapRect() {
		return mArrowsBitmapRect;
	}
	
	private void drawOnScreenControls(Canvas canvas) {

		// Draw the gun image left, top, right, bottom
		// Draw at bottom-right of screen
		//mGunImage.draw(canvas);
		
		canvas.drawBitmap(mPlayer.getWeapon().getCurrentBitmap(), null, mPlayer.getWeapon().getRect(), mSimplePaint);

		// Draw the controls left top right bottom
		// Draw at bottom-left of screen
		//mArrowsImage.draw(canvas);
		canvas.drawBitmap(mBitmaps[mArrowsBitmapIndex], null, mArrowsBitmapRect, mSimplePaint);
		
		//Draw the status text
		canvas.drawText(mStatusText, 0, 20, mTextPaint);
	}
	
	private void moveMonsters() {
		//Animate monsters to the next frame
		for(int i = 0; i < mLevel.getCurrentLevelNumberOfMonstersAndSprites();i++) {
			
			if(mLevel.getCurrentLevelMonstersAndSprites()[i].isMovableItem()) {	
				
				if(mLevel.getCurrentLevelMonstersAndSprites()[i].getHealth() > 0) {
					//Only animate if the sprite can be seen
					if(mLevel.getCurrentLevelMonstersAndSprites()[i].getSeen()) {
						mLevel.getCurrentLevelMonstersAndSprites()[i].animate();
					}
					
					//get the monsters current positional and direction data
					int currentX = mLevel.getCurrentLevelMonstersAndSprites()[i].getX();
					int currentY = mLevel.getCurrentLevelMonstersAndSprites()[i].getY();
					int originalGridX = currentX / TILE_SIZE;
					int originalGridY = currentY / TILE_SIZE;						
					int angle = mLevel.getCurrentLevelMonstersAndSprites()[i].getDirectionAngle();
					int monsterSpeed = mLevel.getCurrentLevelMonstersAndSprites()[i].getSpeed();
					float monsterXDir = mCosTable[angle];
					float monsterYDir = mSinTable[angle];	
					//step the monster
					int newMonsterX = (int) (currentX + (monsterXDir * monsterSpeed));
					int newMonsterY = (int) (currentY + (monsterYDir * monsterSpeed));
					
					//see if they have moved to a new tile in the grid
					int newMonsterGridX = newMonsterX / TILE_SIZE;
					int newMonsterGridY = newMonsterY /TILE_SIZE;						
					if(newMonsterGridX != originalGridX || newMonsterGridY != originalGridY)
					{
						// on a new tile, check if floor, or a sprite, or an open door, or a dead monster
						if(mLevel.isFloor(newMonsterGridY,newMonsterGridX) ||
								mLevel.isSprite(newMonsterGridY,newMonsterGridX) ||
								mLevel.isDoorOpen(newMonsterGridY,newMonsterGridX) || 
								mLevel.isMonsterDead(newMonsterGridY, newMonsterGridX)) {
									//Check if player position
									int playerGridX = mPlayer.getX() / TILE_SIZE;
									int playerGridY = mPlayer.getY() / TILE_SIZE;
									//Check not on player
									if(!(newMonsterGridX == playerGridX && newMonsterGridY == playerGridY)) {
										//Allow the move - update the monster with new position
										//and update the world map
										mLevel.getCurrentLevelMonstersAndSprites()[i].updatePosition(newMonsterX, newMonsterY);
										mLevel.setToFloor(originalGridY, originalGridX);
										mLevel.getCurrentLevelMap()[newMonsterGridY][newMonsterGridX] = mLevel.getCurrentLevelMonstersAndSprites()[i].getType();
									}								
						} else {
							//next position is a wall, door or other monster, so change the 
							//monsters direction, on the next move it will move away from the obstacle
							//Change it by Angle90
							if ((angle += ANGLE90) >= ANGLE360) {
								angle -= ANGLE360;
							}				
							mLevel.getCurrentLevelMonstersAndSprites()[i].setDirectionAngle(angle);								
						}													
					} else {
						//still on the same tile, just update the monsters real position
						mLevel.getCurrentLevelMonstersAndSprites()[i].updatePosition(newMonsterX, newMonsterY);
					}
				}
			}
		}
	}
	
	private void movePlayer() {
		int foundDoorNumber = 0;
		boolean foundWall = false;
		int foundMonsterNumber = 0;
		
		int numberOfStepsToCheckForAll = 6;
		int numberOfStepsToCheckForMonster = 3;
		
		// Walking - forward is (-1), backward is 1
		float playerXDir = mCosTable[mPlayer.getAngle()];
		float playerYDir = mSinTable[mPlayer.getAngle()];		
		int playerX = mPlayer.getX();
		int playerY = mPlayer.getY();
		int playerSpeed = mPlayer.getWalkingSpeed();
		
		//If we are walking
		if (mPlayer.getWalking() != Player.WALK_NONE) {
			
			//Add one step - if a monster or door or wall is not found in the next four 
			//then this is where the player will move to
			
			//Assume walking forward
			int newPlayerX = (int) (playerX + (playerXDir * playerSpeed));
			int newPlayerY = (int) (playerY + (playerYDir * playerSpeed));
			//Not walking forward, so set to backward
			if(mPlayer.getWalking() == Player.WALK_BACKWARD) {
				newPlayerX = (int) (playerX - (playerXDir * playerSpeed));
				newPlayerY = (int) (playerY - (playerYDir * playerSpeed));
			}
			int newPosValue = mLevel.getCurrentLevelMap()[newPlayerY / TILE_SIZE][newPlayerX /TILE_SIZE];
			
			//Check new position is on a floor (cant move onto a wall or monster)
			if (newPosValue == Level.FLOOR_VALUE) {
			
				//Check if a door or monster or wall found in the next few steps
				//to account for our small step size - we currently dont detect
				//a door or monster or wall until we are right on it!		
				int px = playerX;
				int py = playerY;

				for(int i = 0;i<numberOfStepsToCheckForAll;i++) {
									
					//Assume walking forward
					int testPlayerX = (int) (px + (playerXDir * playerSpeed));
					int testPlayerY = (int) (py + (playerYDir * playerSpeed));
					//Not walking forward, so set to backward
					if(mPlayer.getWalking() == Player.WALK_BACKWARD) {
						testPlayerX = (int) (px - (playerXDir * playerSpeed));
						testPlayerY = (int) (py - (playerYDir * playerSpeed));
					}
					
					int testPosValue = mLevel.getCurrentLevelMap()[testPlayerY / TILE_SIZE][testPlayerX /TILE_SIZE];
					
					//check for a door
					if (mLevel.isDoor(testPosValue)) {
						if(mLevel.isDoorOpen(testPosValue)) {
							if(mLevel.isExitDoor(testPosValue)) {
								nextLevel();
								return;
							}
						} else {
							//only block the player if the door is not open
							foundDoorNumber = testPosValue;
							break;
						}
					}//is door
					
					if (mLevel.isWall(testPosValue)) {
						foundWall = true;
						break;
					}
					
					if (mLevel.isSpritePickUp(testPosValue)) {
						mPlayer.pickUpItem(testPosValue);
						//Remove the item from the map
						mLevel.setToFloor(testPlayerY / TILE_SIZE,testPlayerX /TILE_SIZE);
					}
					
					//only check for a monster within 3 steps
					if(i < numberOfStepsToCheckForMonster) {
						if (mLevel.isMonsterAlive(testPosValue)) {
							foundMonsterNumber = testPosValue;
							break;							
						}
					}
					//Nothing found in this step, store the next position and try again
					px = testPlayerX;
					py = testPlayerY;
					
				}
				//If newPlayerX/Y is floor and no monster or door or wall in the next four steps
				//then actually move the player
				if(foundDoorNumber == 0 && !foundWall && foundMonsterNumber == 0) {
					mPlayer.updatePosition(newPlayerX, newPlayerY);
				}
			} else if (mLevel.isDoor(newPosValue)) {
				if(mLevel.isDoorOpen(newPosValue)) {
					if(mLevel.isExitDoor(newPosValue)) {							
						nextLevel();
						return;
					} else {
						mPlayer.updatePosition(newPlayerX, newPlayerY);
					}
				}
			} else if (mLevel.isSprite(newPosValue)) {
				if(mLevel.isSpritePickUp(newPosValue)) {
					mPlayer.pickUpItem(newPosValue);
					//Remove the item from the map
					mLevel.setToFloor(newPlayerY / TILE_SIZE,newPlayerX /TILE_SIZE);
				}
				//Move the player regardless of pick-up or not
				mPlayer.updatePosition(newPlayerX, newPlayerY);
				
			} 
		} else {
			//Not walking, but check if we are close to a monster anyway
			//if we are stationary the monster must be a bit closer to inflict damage
			int px = playerX;
			int py = playerY;
			for(int i = 0;i<numberOfStepsToCheckForMonster;i++) {
			
				int testPlayerX = (int) (px + (playerXDir * playerSpeed));
				int testPlayerY = (int) (py + (playerYDir * playerSpeed));
				int testPosValue = mLevel.getCurrentLevelMap()[testPlayerY / TILE_SIZE][testPlayerX /TILE_SIZE];
				
				if (mLevel.isMonsterAlive(testPosValue)) {
					foundMonsterNumber = testPosValue;
					break;
				}
				//Nothing found in this step, store the next position and try again
				px = testPlayerX;
				py = testPlayerY;			
			}
		}

		if (foundDoorNumber > 0) {
			if(mLevel.isDoorClosed(foundDoorNumber)) {
				mLevel.startDoorOpen(foundDoorNumber);				
			}
		}
		
		if(foundMonsterNumber > 0) {
			mPlayer.hit(mLevel.getMonsterDamage(foundMonsterNumber));
		}
		
	}

	private void nextLevel() {

		setState(STATE_PAUSE);
		
		//Stop moving
		mPlayer.stop();
		
		//Notify up so we can get a dialog displayed
		Message msg = mHandler.obtainMessage();
		Bundle b = new Bundle();
		b.putBoolean("levelComplete", true);
		b.putInt("levelNumber", mLevelNumber);
		msg.setData(b);
		mHandler.sendMessage(msg);
		

		
	}

	/**
	 * Figures the rotation state based on the passage of realtime. Does not
	 * invalidate(). Called at the start of draw(). Detects the end-of-game
	 * and sets the UI to the next state.
	 */
	private void updatePhysics() {
		long now = System.currentTimeMillis();

		// Do nothing if mLastTime is in the future.
		// This allows the game-start to delay the start of the physics
		// by 100ms or whatever.
		if (mLastTime > now)
			return;

		// Once a second update the count of
		// number of frames drawn in the last second
		if (now > (mLastFpsTime + 1000)) {
			mLastFpsTime = now;
			mLastFpsCount = mFps;
			mFps = 0;
			mStatusText = " FPS: " + mLastFpsCount +  " Level " + mLevelNumber + " Health: " + mPlayer.getHealth() + " Ammo: " + mPlayer.getWeapon().getAmmo();// + "Angle: " + mPlayer.getAngle();// + " Enemy Health: " + mMonsterHealth;
		} else {
			mFps++;
		}
		
		if (now > (mLastFpsTime + 800)) {
			moveMonsters();	
		}
		
		// Also if we are firing then dont show it
		// for a bit		
		if(mPlayer.getFiring() && mPlayer.getWeapon().getAmmo() > 0) {
			if (now > (mLastFpsTime + 400)) {
				mPlayer.getWeapon().reduceAmmo();
				mShowFiring = mShowFiring == true ? false : true;
			}
			doFiring();
		}
		
		//Open the doors as quick as possible
		for(int d = 0;d< mLevel.getCurrentLevelNumberOfDoors();d++) {
			if(mLevel.getCurrentLevelDoors()[d].getState() == Door.DOOR_OPENING) {
				mLevel.getCurrentLevelDoors()[d].doOpen();
			} else if(mLevel.getCurrentLevelDoors()[d].getState() == Door.DOOR_CLOSING) {
				mLevel.getCurrentLevelDoors()[d].doClose();
			}
		}

		
		//Player movement
		mPlayer.doRotation();			
		movePlayer();

		//Check the player is alive
		if(mPlayer.getHealth() <= 0) {
			
			setState(STATE_LOSE);
			
			//Stop moving
			mPlayer.stop();
			
			//Notify up so we can get a dialog displayed
			Message msg = mHandler.obtainMessage();
			Bundle b = new Bundle();
			b.putBoolean("playerDied", true);
			msg.setData(b);
			mHandler.sendMessage(msg);
			
			return;
		}
		
		mLastTime = now;

	}// end updatePhysics method

	private void doFiring() {
		
		if(mPlayer.getWeapon().getAmmo() > 0 ) {
			for (int x = 0; x < mLevel.getCurrentLevelNumberOfMonstersAndSprites(); x++) {
						
				//Cant use a test monster as we may need to modify it
				
				//If a monster, and seen, and its alive...
				if(mLevel.getCurrentLevelMonstersAndSprites()[x].isMonster() 
						&& mLevel.getCurrentLevelMonstersAndSprites()[x].getSeen()
						&& mLevel.getCurrentLevelMonstersAndSprites()[x].getHealth() > 0) {
	
					//if a monster is hit, change its angle so it comes 
					//towards the player
					int hitMonsterAngle = mPlayer.getAngle();
					if ((hitMonsterAngle += ANGLE180) >= ANGLE360) {
						hitMonsterAngle -= ANGLE360;
					}	
					
						int startFireHit = mPlayer.getWeapon().getStartFireHit();
						int endFireHit = mPlayer.getWeapon().getEndFireHit();
					
						//There are 4 possible fire hits
						//1 - fire wholly inside monster
						//2 - fire complete outside monster
						//3 - overlap left
						//4 - overlap right
						boolean hit = false;
						if(startFireHit >= mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtFirstColumn() && endFireHit <= mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtLastColumn()) {
							mLevel.getCurrentLevelMonstersAndSprites()[x].hit(mPlayer.getWeapon(),hitMonsterAngle);
							hit = true;
						} else if(startFireHit <= mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtFirstColumn() && endFireHit >= mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtLastColumn()) {
							mLevel.getCurrentLevelMonstersAndSprites()[x].hit(mPlayer.getWeapon(),hitMonsterAngle);
							hit = true;
						} else if (startFireHit <= mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtLastColumn() && endFireHit >= mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtLastColumn()) {
							mLevel.getCurrentLevelMonstersAndSprites()[x].partialHit(mPlayer.getWeapon(),hitMonsterAngle);
							hit = true; 
						} else if(startFireHit <= mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtFirstColumn() && endFireHit >= mLevel.getCurrentLevelMonstersAndSprites()[x].getSeenAtFirstColumn()) {
							mLevel.getCurrentLevelMonstersAndSprites()[x].partialHit(mPlayer.getWeapon(),hitMonsterAngle);
							hit = true;					 
						}
						if(hit) {
							mStatusText = " FPS: " + mLastFpsCount + " Level " + mLevelNumber + " Health: " + mPlayer.getHealth() + " Ammo: " + mPlayer.getWeapon().getAmmo() + " Enemy Health: " + mLevel.getCurrentLevelMonstersAndSprites()[x].getHealth();
						}
				}//seen
			}//for]
			mPlayer.getWeapon().reduceAmmo();
		}//has ammo
		
		
	}//end doFiring

	/***
	 * Called when the activity is destroyed - recycle all the bitmaps
	 */
	public void onDestroy() {
        
		synchronized (mSurfaceHolder) {
	        //No longer run the thread
	        mRun = false;
			
			for (int x = 0; x < mBitmaps.length; x++) {
	        	if(mBitmaps[x] != null) {
	        		mBitmaps[x].recycle();
	        		mBitmaps[x] = null;
	        	}
	        }
			
			mLevel.destroy();
			mPlayer.destroy();

		}
        
	}

	public int getLevelNumber() {
		return mLevelNumber;
	}

	public void moveToNextLevel() {

		mLastTime = System.currentTimeMillis() + 100;

		//Move to the next level
		mLevelNumber = mLevel.loadNextLevel();
		mPlayer.useNewLevelStartPositions(mLevel.getCurrentLevelPlayerStartX(), mLevel.getCurrentLevelPlayerStartY());
		mPlayer.reset();
		
		setState(STATE_RUNNING);
		
	}

	//Used by the view to determine if the gun has been touched
	public Rect getGunBitmapRect() {
		return mPlayer.getWeapon().getRect();
	}

}// End RaycastThread class
